package se.webbzon.oschi01.worldobject;

import javax.media.j3d.Transform3D;
import javax.vecmath.AxisAngle4d;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;

import se.webbzon.oschi01.collision3d.CollisionNode3D;
import se.webbzon.oschi01.friendly.Friendly;
import se.webbzon.oschi01.worldarea.WorldArea;
import se.webbzon.oschi01.worldobject.appearance.Appearance3D;

public abstract class WorldStaticObject extends WorldObject implements Cloneable {
	
	// The position of the instance
	private Vector3d position;
	
	// The size (scaling) of the instance
	private Vector3d size;
	
	// The rotation of the instance
	private AxisAngle4d rotation;
	
	// The transform of the instance
	// (contains translation, scaling and rotation)
	private Transform3D transform;
	
	// The mask (collision model) of the instance
	private CollisionNode3D mask;
	
	// The appearance of the instance
	private Appearance3D appearance;
	
	// Whatever the instance is solid or not
	private boolean solid;
	
	// The group this instance belongs to
	private WorldGroup group;
	
	// Whatever the instance has changed during this step
	private boolean changed = true;

	/*============================================================
	Constructors
	============================================================*/
	
	/** Creates a new abstract WorldStaticObject with a given appearance and
	 * a given set of registered methods. The 2D-depth of the instance will 
	 * be equal to zero. **/
	public WorldStaticObject(int registeredMethods, Appearance3D appearance) {
		this(registeredMethods,0,appearance);
	}
	
	/** Creates a new abstract WorldStaticObject with a given appearance and
	 * a given set of registered methods. The 2D-depth of the instance is given
	 * by the input depth2D. **/
	public WorldStaticObject(	int registeredMethods, int depth2D, 
								Appearance3D appearance) {
		super(registeredMethods | WorldObject.onStepEnd);
		this.appearance = appearance;
		this.group = null;
		
		position = new Vector3d();
		size = new Vector3d(1,1,1);
		rotation = new AxisAngle4d();
		transform = new Transform3D();
	}
	
	/*============================================================
	Public Methods
	============================================================*/
	
	/** Copies the position of the instance into the given point. **/
	public void getPosition(Point3d position) {
		position.set(this.position);
	}
	
	/** Returns a copy of the position of this instance. **/
	public Point3d getPosition() {
		return new Point3d(position);
	}
	
	/** Copies the translation of the instance into the given vector.
	 * This will return the same result as getPosition() but
	 * the output will be a vector instead of a point. **/
	public void getTranslation(Vector3d translation) {
		translation.set(position);
	}
	
	/** Returns a copy of the translation of this instance. 
	 * This will return the same result as getPosition() but
	 * the output will be a vector instead of a point. **/
	public Vector3d getTranslation() {
		return new Vector3d(position);
	}
	
	/** Return the x-position of the instance. **/
	public double getX() {
		return position.x;
	}
	
	/** Return the y-position of the instance. **/
	public double getY() {
		return position.y;
	}
	
	/** Return the z-position of the instance. **/
	public double getZ() {
		return position.z;
	}
	
	/** Sets the position of the instance. **/
	public void setPosition(double x, double y, double z) {
		if (position.x != x || position.y != y || position.z != z) {
			position.set(x, y, z);
			changed = true;
			//transform.setTranslation(position);
			//applyTransform(transform);
		}
	}
	
	/** Copies the rotation of the instance into the given axis-angle. */
	public void getRotation(AxisAngle4d rotation) {
		rotation.set(this.rotation);
	}
	
	/** Returns a copy of the rotation of the instance. **/
	public AxisAngle4d getRotation() {
		return new AxisAngle4d(rotation);
	}
	
	/** Returns the x-position of the Euler vector describing the rotation
	 * axis of the instance. **/
	public double getEX() {
		return rotation.x;
	}
	
	/** Returns the y-position of the Euler vector describing the rotation
	 * axis of the instance. **/
	public double getEY() {
		return rotation.y;
	}
	
	/** Returns the z-position of the Euler vector describing the rotation
	 * axis of the instance. **/
	public double getEZ() {
		return rotation.z;
	}
	
	/** Returns the rotation angle of the instance. The instance is rotated
	 * around the given Euler vector. **/
	public double getAngle() {
		return rotation.angle;
	}
	
	/** Sets the rotation of the instance by using an axis-angle
	 * representation. **/
	public void setRotation(double x, double y, double z, double angle) {
		if (rotation.angle != angle || rotation.x != x || rotation.y != y || rotation.z != z) {
			rotation.set(x, y, z, angle);
			changed = true;
			//transform.set
			//transform.setRotation(rotation);
			//applyTransform(transform);
		}
	}
	
	/** Returns the appearance of the instance. **/
	public Appearance3D getAppearance() {
		return appearance;
	}
	
	/** Returns the mask (collision model) of the instance. **/
	public CollisionNode3D getMask() {
		return mask;
	}
	
	/** Sets the mask (collision model) of the instance. **/
	public void setMask(CollisionNode3D mask) {
		this.mask = mask;
		if (mask != null)
			mask.setTransform(transform);
	}
	
	/** Returns true if the instance is solid. **/
	public boolean isSolid() {
		return solid;
	}
	
	/** Sets whatever the instance is solid or not. **/
	public void setSolid(boolean solid) {
		this.solid = solid;
	}
	
	/** Returns true if the instance is solid and has a mask.**/
	public boolean hasSolidMask() {
		return (mask != null && solid);
	}
	
	/** Returns true if the instance is visible. **/
	public boolean isVisible() {
		return appearance.getVisible();
	}
	
	/** Sets whatever the instance is visible of not. **/
	public void setVisible(boolean visible) {
		appearance.setVisible(visible);
	}
	
	/** Returns the width of the instance (x-scaling). **/
	public double getWidth() {
		return size.x;
	}
	
	/** Returns the height of the instance (y-scaling). **/
	public double getHeight() {
		return size.y;
	}
	
	/** Returns the depth of the instance (z-scaling). **/
	public double getDepth() {
		return size.z;
	}
	
	/** Copies the size of the instance into the given vector. **/
	public void getSize(Vector3d size) {
		size.set(this.size);
	}
	
	/** Returns a copy of the size of the instance. **/
	public Vector3d getSize() {
		return new Vector3d(size);
	}
	
	/** Sets the size of the instance (scaling). **/
	public void setSize(double width, double height, double depth) {
		if (size.x != width || size.y != height || size.z != depth) {
			size.set(width, height, depth);
			changed = true;
			//transform.setScale(size);
			//applyTransform(transform);
		}
	}
	
	/** Returns the group this instance belongs to. **/
	public WorldGroup getGroup() {
		return group;
	}
	
	/** Returns the distance to another WorldStaticObject. **/
	public double distanceTo(WorldStaticObject other) {
		final Vector3d translation = new Vector3d(position);
		translation.sub(other.position);
		return translation.length();
	}
	
	/** Returns the distance to a given point. **/
	public double distanceTo(Point3d position) {
		final Vector3d translation = new Vector3d(position);
		translation.sub(this.position);
		return translation.length();
	}
	
	/** Copies the transform of this instance to the given Transform3D. 
	 * The transform if the result of the translation, scaling and 
	 * rotation of the instance. **/
	public void getTransform(Transform3D transform) {
		transform.set(this.transform);
	}
	
	/** Refreshes the appearance and/or the mask of this instance by
	 * using the current translation, scaling and rotation. An allocated
	 * Transform3D must be provided. **/
	public void refreshTransform(boolean appearance, boolean mask) {
		refreshTransform(transform,appearance,mask);
	}
	
	@Override public void onStepEnd(WorldArea world) {
		if (changed) {
			refreshTransform(true,true);
			changed = false;
		}
		
		if (appearance != null)
			appearance.onStepEnd();
	}
	
	/*============================================================
	Protected Methods
	============================================================*/
	
	/** Refreshes the appearance and/or the mask of this instance by
	 * using the current translation, scaling and rotation. The result
	 * of the transformation will be put in the provided Transform3D instance. **/
	protected void refreshTransform(Transform3D result, boolean appearance, boolean mask) {
		final boolean inGroup = group != null;
		final double[] oldTransform;
		transform.setIdentity();
		transform.setTranslation(position);
		transform.setScale(size);
		transform.setRotation(rotation);

		/** Multiply by group transform. **/
		if (inGroup) {
			oldTransform = new double[16];
			transform.get(oldTransform);
			group.transform(transform);
		} else
			oldTransform = null;

		/** Transform **/
		if (appearance)
			this.appearance.setTransform(transform);
		if (mask && this.mask != null)
			this.mask.setTransform(transform);

		/** Restore old transform. **/
		if (inGroup)
			transform.set(oldTransform);
	}
	
	/** Applies a transform on this instance. The transform will affect
	 * the translation, scaling and rotation of the instance. This method
	 * is invoked automatically when setPosition, setSize or setRotation 
	 * are called.**/
	/*protected void applyTransform(Transform3D transform) {
		if (group != null) {
			// Store the old transform
			final double[] t = new double[16];
			transform.get(t);
			group.transform(transform);
			
			appearance.setTransform(transform);
			if (mask != null)
				mask.setTransform(transform);
			
			// Reset the old transform
			transform.set(t);
		} else {
			appearance.setTransform(transform);
			if (mask != null)
				mask.setTransform(transform);
		}
	}*/
	
	/*============================================================
	Friendly Methods
	============================================================*/
	
	/** Sets the group this instance belongs to. The instance will be 
	 * affected by the transform of the given group. **/
	@Friendly void setGroup(WorldGroup group) {
		this.group = group;
		refreshTransform(true,true);
		//applyTransform(transform);
	}

}
